package roborally;
import roborally.IllegalCoordException;
import be.kuleuven.cs.som.annotate.*;

//TODO coord neg exception + constructor aanpassen LIES
// ENEGRIE --> NOMINAAL (pre-condities)
// PLAATS,ORIENTATIE --> DEFENSIEF (exceptions)
/**
 * A class of robots with long x- and y-coordinate, integer orientation and double energy.
 * 
 * TODO invar --> nog??
 * @invar	The robot must have a legal set of coordinates.
 *			| isLegalCoord(getxCoord()) && isLegalCoord(getyCoord())
 * @invar	The robot must have a legal amount of energy.
 * 			| isLegalEnergy(getEnergy())
 * @invar	The robot must have a valid orientation.
 * 			| isLegalOrientation(getOrientation())
 * @version	1.0
 * @author Lies Tambeur & Eline Vanrykel
 *
 */
public class Robot implements IRobot{
	
	//TODO uitleg Lies
	private long xCoord;
	private long yCoord;
	private double energy;
	private int orientation;
	
	/**
	 * The constant reflecting the energy required for 1 turn of a robot.
	 */
	private static final double ENERGY_FOR_TURN = 100;
	/**
	 * The constant reflecting the energy required for 1 move of a robot.
	 */
	private static final double ENERGY_FOR_MOVE = 500;
	/**
	 * The constant reflecting the highest possible value for the size of the playing board.
	 */
	private static final long MAX_COORD = Long.MAX_VALUE;
	/**
	 * The constant reflecting the maximum amount of energy a robot can possess, expressed in Ws.
	 */
	private static final double MAX_ENERGY = 20000; 
	
	/**
	 * Initialize this new robot with the given coordinates, orientation and energy.
	 * 
	 * @param 	xCoord
	 * 			The x-coordinate for this new robot.
	 * @param 	yCoord
	 * 			The y-coordinate for this new robot.
	 * @param 	orientation
	 * 			The orientation for this new robot. 0, 1, 2, 3 respectively represent up, right, down and left.
	 * @param 	energy
	 * 			The energy for this new robot.
	 * @post	If the given x-coordinate is <code>null</code> then the x-coordinate will be set on 0.
	 * 			| if xCoord==null
	 * 			|	then new.getxCoord()==0
	 * @post	If the absolute value of the given x-coordinate is not legal, it will be set on MAX_COORD.
	 * 			| if (!isLegalCoord(xCoord))
	 * 			|	then new.getxCoord()==MAX_COORD
	 * @post	If the absolute value of the given x-coordinate is not <code>null</code> and is legal, the x-coordinate will be set on absolute value of the given x-coordinate.
	 * 			| if (xCoord!=null && isLegalCoord(Math.abs(xCoord)))
	 * 			|	then new.getxCoord()==Math.abs(xCoord)
	 * @post	If the given y-coordinate is <code>null</code> then the y-coordinate will be set on 0.
	 * 			| if yCoord==null
	 * 			|	then new.getyCoord()==0
	 * @post	If the absolute value of the given y-coordinate is not legal, it will be set on MAX_COORD.
	 * 			| if (!isLegalCoord(yCoord))
	 * 			|	then new.getyCoord()==MAX_COORD
	 * @post	If the absolute value of the given y-coordinate is not <code>null</code> and is legal, the y-coordinate will be set on the absolute value of the given y-coordinate.
	 * 			| if (yCoord!=null && isLegalCoord(yCoord))
	 * 			|	then new.getyCoord()==yCoord
	 * @post	If the given orientation is <code>null</code> or not legal, the orientation of the robot will be set 1, respectively right.
	 * 			| if(orientation == null || !isLegalOrientation(orientation))
	 * 			|	then new.getOrientation()==1
	 * @post	If the given orientation is not null and is legal, the orientation of the robot will be set on the given orientation.
	 * 			| if(!(orientation ==null) && isLegalOrientation(orientation))
	 * 			|	then new.getOrientation==orientation
	 */
	public Robot(long xCoord, long yCoord, int orientation, double energy){ //throws IllegalCoordException{
		
		try{
			this.setxCoord(xCoord);
		} catch (NullPointerException e){
			try{
				this.setxCoord(0);
			}
			catch (IllegalCoordException e2) {
				//kan niet!
			}
		} catch (IllegalCoordException e){
			try{
				this.setxCoord(MAX_COORD);
			}
			catch (IllegalCoordException e2){
				// kan niet
			}
				
		} catch (IllegalArgumentException e){
			try{
				this.setxCoord(Math.abs(xCoord));
			} catch (IllegalCoordException exc){
				try{
					this.setxCoord(MAX_COORD);
				}
				catch(IllegalCoordException e2){
					// kan niet
				}
			}
		}
		
		try{
			this.setyCoord(yCoord);
		} catch (NullPointerException e){
			try{
				this.setyCoord(0);
			}
			catch(IllegalCoordException e2){
				//kan niet
			}
		} catch (IllegalCoordException e){
			try{
				this.setyCoord(MAX_COORD);
			}
			catch(IllegalCoordException e2){
			// kan niet	
			}
		} catch (IllegalArgumentException e){
			try{
				this.setyCoord(Math.abs(yCoord));
			} catch (IllegalCoordException exc){
				try{
					this.setyCoord(MAX_COORD);
				}
				catch(IllegalCoordException e2) {
					// kan niet
				}
			}
		}
		
		try{
			this.setOrientation(orientation);
		} catch (NullPointerException e){
			this.setOrientation(1);
		} catch (IllegalArgumentException e){
			this.setOrientation(1);
		}
		
		this.setEnergy(energy);
		
		//TODO: kheb da ff laten staan in commentaar, de vorige versie van de constructor, ma nu moet er geen
		// extra exception meer opgegooid worden in constructor, want da is verveled voor bij de testklasse...
		// Da is tog beter zo he?
		
//		try{
//			this.setxCoord(xCoord);
//		} catch (NullPointerException e){
//			this.setxCoord(0);
//		} catch (IllegalCoordException e){
//			this.setxCoord(MAX_COORD);
//		} catch (IllegalArgumentException e){
//			try{
//				this.setxCoord(Math.abs(xCoord));
//			} catch (IllegalCoordException exc){
//				this.setxCoord(MAX_COORD);
//			}
//		}
//		
//		try{
//			this.setyCoord(yCoord);
//		} catch (NullPointerException e){
//			this.setyCoord(0);
//		} catch (IllegalCoordException e){
//			this.setyCoord(MAX_COORD);
//		} catch (IllegalArgumentException e){
//			try{
//				this.setxCoord(Math.abs(xCoord));
//			} catch (IllegalCoordException exc){
//				this.setxCoord(MAX_COORD);
//			}
//		}
//		
//		try{
//			this.setOrientation(orientation);
//		} catch (NullPointerException e){
//			this.setOrientation(1);
//		} catch (IllegalArgumentException e){
//			this.setOrientation(1);
//		}
//		
//		this.setEnergy(energy);
	}
	
	
	/**
	 * Returns the x-coordinate of the robot. 
	 * @return	The x-coordinate of the robot.  	
	 * 			| result == this.xCoord
	 */
	@Basic
	public long getxCoord() {
		return xCoord;
	}
	
	/**
	 * Sets the x-coordinate of the robot to the given value.
	 * @param  	xCoord
	 * 		   	The new x-coordinate for this robot.
	 * @post	The new x-coordinate of this robot is equal to value of the given x-coordinate.
	 * 			| new.getxCoord() == xCoord
	 * @throws	IllegalCoordException
	 * 			The value of the given x-coordinate is not a valid coordinate for a robot.
	 * 			| !isLegalCoord(xCoord)
	 * @throws	IllegalArgumentException
	 * 			The value of the given x-coordinate is negative.
	 * 			| xCoord < 0
	 */
	@Basic
	public void setxCoord(long xCoord) throws IllegalCoordException, IllegalArgumentException{
		if(xCoord<0){
			throw new IllegalArgumentException();
		}
		if(!isLegalCoord(xCoord)){
			throw new IllegalCoordException(xCoord);
		}
		this.xCoord = xCoord;
	}
	
	/**
	 * Returns the y-coordinate of the robot. 
	 * @return	The y-coordinate of the robot.  
	 * 			| result == this.yCoord	
	 */
	@Basic
	public long getyCoord() {
		return yCoord;
	}
	
	/**
	 * Sets the y-coordinate of the robot to the given value.
	 * @param  	yCoord
	 * 		   	The new y-coordinate for this robot.
	 * @post	The new y-coordinate of this robot is equal to value of the given y-coordinate.
	 * 			| new.getxCoord() == yCoord
	 * @throws	IllegalCoordException
	 * 			The value of the given y-coordinate is not a valid coordinate for a robot.
	 * 			| !isLegalCoord(yCoord)
	 * @throws	IllegalArgumentException
	 * 			The value of the given y-coordinate is negative.
	 * 			| yCoord < 0
	 */
	@Basic
	public void setyCoord(long yCoord) throws IllegalCoordException, IllegalArgumentException{
		if(yCoord<0){
			throw new IllegalArgumentException();
		}
		if(!isLegalCoord(yCoord)){
			throw new IllegalCoordException(yCoord);
		}
		this.yCoord = yCoord;
	}
	
	/**
	 * Returns the amount of energy of the robot.
	 * @return	The current energy of the robot.
	 * 			| result == this.energy
	 */
	@Basic
	public double getEnergy() {
		return energy;
	}
	
	/**
	 * Sets the energy of the robot to the given amount, if the given amount is valid.
	 * @param 	energy
	 * 			The new energy for this robot.
	 * @pre	The given amount of energy must be a valid amount of energy.
	 * 		| isValidEnergy(energy)
	 * @post	The energy of this robot is equal to the given energy.
     *       	| new.getEnergy() == energy
	 */
	@Basic
	public void setEnergy(double energy) {
		assert(isLegalEnergy(energy));
			this.energy = energy;
		
	}
	
	/**
	 * Returns the orientation of the robot.
	 * @return	The orientation 0, 1, 2, 3 respectively represent up, right, down and left.
	 * 			| result == this.orientation
	 */
	@Basic
	public int getOrientation() {
		return orientation;
	}
	
	/**
	 * Sets the orientation of the robot, if the orientation is valid.
	 * @param 	orientation
	 * @post	The orientation of this robot is equal to the given orientation.
	 * 			| new.getOrientation() == orientation;
	 * @throws	IllegalArgumentException
	 * 			The given orientation is not a valid orientation for a robot.
	 * 			| !isLegalOrientation(orientation)
	 */
	@Basic
	public void setOrientation(int orientation) throws IllegalArgumentException{
		if (!isLegalOrientation(orientation))
			throw new IllegalArgumentException();
		this.orientation = orientation;
	}
	
	/**
	 * Checks if the given coordinate is a valid coordinate.
	 * @param		coord
	 * 				The coordinate that will be checked if it is a valid coordinate.
	 * @return		True if and only if the absolute value of the given coordinate is smaller 
	 * 				than the highest possible value for the size of the playing board;
	 * 				| result == (Math.abs(coord)<=MAX_COORD)
	 */
	private static boolean isLegalCoord(long coord){
		if(coord>MAX_COORD){  //TODO: ook controleren of neg? is niet nodig maar stel dat we hier tg ingraken...
			return false;
		}
		return true;
	}
	
	/**
	 * Checks whether a given amount of energy is a valid amount of energy.
	 * @param 	energy
	 * 			The amount of energy that will be checked if it is a legal amount of energy.
	 * @return	True if and only if the given amount of energy is positive or equal to zero and smaller 
	 * 			or equal than the maximum amount of energy a robot can possess.
	 * 			| result == (energy >= 0 && energy <= MAX_ENERGY)
	 */
	private static boolean isLegalEnergy(double energy) {
		return (energy >= 0 && energy <= MAX_ENERGY);
	}
	
	/**
	 * Checks whether a given orientation is a valid orientation.
	 * @param	orientation
	 * 			The orientation to be checked.
	 * @return	True if and only if the given orientation is equal to 0,1,2 or 3.
	 * 			| result == (orientation >= 0 && orientation < 4)
	 */
	private static boolean isLegalOrientation(int orientation){
		return (orientation >= 0 && orientation < 4);
	}
	
	/**
	 * Returns the amount of energy as a fraction of the maximum amount of energy.
	 * @return	The amount of energy as a fraction of the maximum amount of energy.
	 * 			| result == this.getEnergy()/MAX_ENERGY
	 */
	public double getRelativeEnergy(){
		assert MAX_ENERGY != 0;
		return (this.getEnergy()/MAX_ENERGY);
	}
	
	/**
	 * Increase the amount of energy with a given amount of energy.
	 * 
	 * @param 	energy
	 * 			The amount of energy to be added to the current amount of energy.
	 * @pre	The given energy amount must be positive.
     *      | energy > 0
	 * @effect	The energy amount is set to its current contents
     *         	incremented with the given amount of energy.
     *       	| this.setEnergy(energy + this.getEnergy());
	 */
	public void recharge(double energy){
		assert energy > 0;
		this.setEnergy(energy + this.getEnergy());
	}
	
	/**
	 * TODO uitleg methode
	 * TODO vragen: exceptions throwen?
	 * Turns the robot 90 degrees clockwise.
	 * @post	The new orientation is the old orientation plus one modulo 4.
	 * 			| new.getOrientation == ((this.getOrientation() + 1)%4)
	 * @effect	The energy amount is set to its current contents
     *         	decreased with ENERGY_FOR_TURN.
     *       	| this.setEnergy(energy + this.getEnergy());
	 */
	public void turnClockwise(){ 
		this.setEnergy(this.getEnergy()-ENERGY_FOR_TURN); //TODO : ? als er niet genoeg energie is, stopt dit dan hier?
		this.setOrientation((this.getOrientation()+1)%4); //TODO : heb dit met modulo gedaan, klopt dit want is veel korter als ervoor		
																//ja, dit is juist!, tegenwijzerzin is wel fout... (zie uitleg hieronder)
	}
	
	/**
	 * TODO uitleg methode
	 * TODO vragen: exceptions throwen?
	 * Turns the robot 90 degrees counterclockwise.
	 * @post	The new orientation is the absolute value of the old orientation minus one modulo 4.
	 * 			| new.getOrientation == ((Math.abs(this.getOrientation() - 1))%4)
	 * @effect	The energy amount is set to its current contents
     *         	decreased with the ENERGY_FOR_TURN.
     *       	| this.setEnergy(energy + this.getEnergy());
	 */
	public void turnCounterClockwise(){ 
		this.setEnergy(this.getEnergy()-ENERGY_FOR_TURN); //TODO : ? als er niet genoeg energie is, stopt dit dan hier? 
															//--> anders moet je een testje doen, als te weinig energie hebt doe een sysout en gebeurt er niets, in het andere geval wel... (mbv isLegalEnergy()), maja das eigenlijk nogal stom da we ni met exceptions werken, da is da veel gemakkelijker op te lossen...
		this.setOrientation((Math.abs(this.getOrientation()-1))%4); //TODO : heb dit met modulo gedaan, klopt dit want is veel korter als ervoor	
																	// TODO dit is niet juist denk ik, want als orientatie = 0, dan wordt (Math.abs(this.getOrientation()-1))=1 , dan 1 mod 4 = 1 ipv 3
																	// zonder absolute waarde werkt dat ook niet want in java is -1 mod 4 = -1 en niet 3, -2 mod 4 = -2 en niet 2
																	// en ook -1 mod (-4) = 1, ik vrees dat we beter hier gwn een testje maken if orientatie == 0 ==> nieuwe orienatie = 3
	}
	
	/**
	 * Moves the robot one place forward, depending on its orientation.
	 * 
	 * @effect	The corresponding coordinate of the robot will be increased/decreased, depending on the robot's orientation.
	 * 		TODO of beter met @post?? + uitleg goed, of moet ik dat echt geval per geval gaan uitleggen in woorden??
	 * 			| (this.getOrientation==0) == (this.setyCoord(this.getyCoord()-1))
	 * 			| (this.getOrientation==1) == (this.setyCoord(this.getxCoord()+1))
	 * 			| (this.getOrientation==2) == (this.setyCoord(this.getyCoord()+1))
	 * 			| (this.getOrientation==3) == (this.setyCoord(this.getxCoord()-1))
	 * 
	 */
	public void move(){ 
		switch(this.getOrientation()){
		case 0: try {
				this.setyCoord(this.getyCoord()-1);
			} catch (IllegalArgumentException | IllegalCoordException e) {
				System.out.println("out of board"); //TODO: aparte methode voormaken (code duplicatie nu...) 
													// alleen catch in aparte methode kan niet... en een methode maken 
													// om 1 zin te printen is ook ene beetje belachelijk....
			} break;
		case 1: try {
				this.setxCoord(this.getxCoord()+1);
			} catch (IllegalArgumentException | IllegalCoordException e) {
				System.out.println("out of board");
			} break;
		case 2: try {
				this.setyCoord(this.getyCoord()+1);
			} catch (IllegalArgumentException | IllegalCoordException e) {
				System.out.println("out of board");
			} break;
		case 3: try {
				this.setxCoord(this.getxCoord()-1);
			} catch (IllegalArgumentException | IllegalCoordException e) {
				System.out.println("out of board");
			} break;
		}
	}
	
	/**
	 * Method calculating the minimal amount of energy required by the robot to reach
	 * a particular position, assuming the robot doesn't make inefficient moves/turns.
	 * 
	 * @param 	xCoord
	 * 			The x-coordinate of the position the robot wants to reach.
	 * @param 	yCoord
	 * 			The y-coordinate of the position the robot wants to reach.
	 * @return	The minimal amount of energy required by the robot to reach the particular position.
	 * 			| result == TODO ELINE, LIES: moeten al die if-testjes daarin staan???? 
	 */
	public double getMinEnergyToReach(long xCoord, long yCoord){
		int numberOfTurns = 2;
		if(this.getxCoord() == xCoord && this.getyCoord() == yCoord)
			return 0.0;
		//TODO: LIES zou je eens kunnen nakijken of dat dit juist is? Ik denk het wel, ma tis zo'n lange uitdrukking...
		if( (this.getxCoord() == xCoord && //Dit zou ook in 2 ondergeschikte if-testen kunnen, beter zo of niet? mss wel sneller...
						( (this.getyCoord()<yCoord && this.getOrientation() == 2) 
								|| (this.getyCoord()>yCoord && this.getOrientation() == 0) ) )
				|| (this.getyCoord() == yCoord &&
						( (this.getxCoord()<xCoord && this.getOrientation() == 1) 
								|| (this.getxCoord()>xCoord && this.getOrientation() == 3) ) ) ){
			numberOfTurns = 0;
		}
		if( 		(this.getOrientation() == 0 && this.getyCoord()>=yCoord) 
				||  (this.getOrientation() == 2 && this.getyCoord()<=yCoord) 
				||  (this.getOrientation() == 1 && this.getxCoord()<=xCoord) 
				||  (this.getOrientation() == 3 && this.getxCoord()>=xCoord) ){
			numberOfTurns =1;
		}			
		return (Math.abs(this.getxCoord()-xCoord) + Math.abs(this.getyCoord()-yCoord))*ENERGY_FOR_MOVE 
				+ numberOfTurns*ENERGY_FOR_TURN;
	}
				
	/**
	 * TODO LIES + zie beperkingen in opgave
	 * @param otherRobot
	 */
	public void moveNextTo(IRobot otherRobot){
		if(getMinEnergyToReach(otherRobot.getxCoord(), otherRobot.getyCoord())<=this.getEnergy()){
			
		}
		
	}
	

}
